
#ifndef __MLS_MESHDEFORMERFS_H_GL_
#define __MLS_MESHDEFORMERFS_H_GL_

#include "../glslKernel.h"
#include "../fboT.h"

#include "../core/mlsDeformer.h"
#include "mesh.h"

class MlsMeshDeformerFS : public MlsDeformer<TConstraints<Point3> > {
   
   typedef TConstraints<Point3>     PConstraints;
public:
   ~MlsMeshDeformerFS() {}
   
   MlsMeshDeformerFS()
      :MlsDeformer<PConstraints>::MlsDeformer(), model(0) {
   }
   
   /// 
   /// @param m the mesh model
   /// @param constraints the initial constraints
   MlsMeshDeformerFS(Mesh *m, const PConstraints &constraints)
      :MlsDeformer<PConstraints>::MlsDeformer() {
      
      // copy initial positions and normals
      assert(m != 0);
      model = m;
      int nv = model->nvertices();
      nv = (int)(ceil(sqrt(nv)));
      ipositions.resize(nv*nv); // squared size for readpixel 
      inormals.resize(nv*nv); // squared size for readpixel 
      Vertex_iterator vi = model->vertices_begin();
      for (unsigned int i=0; i<model->nvertices(); ++i, ++vi) {
         ipositions[i] = vi->point();
         inormals[i] = vi->normal();
      }
      
      ctrlPointsSize = constraints.size();
      glActiveTexture(GL_TEXTURE1);
      glGenTextures(1, &texP);
      glBindTexture(GL_TEXTURE_RECTANGLE_NV, texP);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_RGB32_NV, 1, ctrlPointsSize, 0, 
                   GL_RGB, GL_FLOAT, &constraints.positions[0]);
      
      glActiveTexture(GL_TEXTURE2);
      glGenTextures(1, &texQ);
      glBindTexture(GL_TEXTURE_RECTANGLE_NV, texQ);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_RGB32_NV, 1, ctrlPointsSize, 0, 
                   GL_RGB, GL_FLOAT, &constraints.positions[0]);
      
      GLuint texPositions, texNormals;
      int n = ipositions.size();
      posTexSize = (int)(ceil(sqrt(n)));
      
      glActiveTexture(GL_TEXTURE3);
      glGenTextures(1, &texNormals);
      glBindTexture(GL_TEXTURE_2D, texNormals);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F_ARB, posTexSize, posTexSize, 
                   0, GL_RGB, GL_FLOAT, &inormals[0]);
      
      glActiveTexture(GL_TEXTURE0);
      glGenTextures(1, &texPositions);
      glBindTexture(GL_TEXTURE_2D, texPositions);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F_ARB, posTexSize, posTexSize, 
                   0, GL_RGB, GL_FLOAT, &ipositions[0]);
      
      /// insert the number of control points into the source shader
      ostringstream oscpNumber;
      oscpNumber<<constraints.size();
      string firstline;
      firstline = "#define NCTRLPOINTS " + oscpNumber.str() + " \n";
      string shaderFileName = "mlsrigid.fs";
      ofstream fileout(shaderFileName.c_str(), ios::in | ios::out | ios::binary);
      assert(fileout);
      fileout.seekp(0, ios::beg);
      fileout<<firstline;
      fileout.close();
      
      mlsKernel.fragment_source("mlsrigid.fs");
      fbo.setNumberOfChannels(4);
      fbo.initFBO(posTexSize, posTexSize); // FBO initialization
      mlsKernel.install(true);
      
      mlsKernel.use(true);
      mlsKernel.set_uniform("positions", 0);
      mlsKernel.set_uniform("P", 1);
      mlsKernel.set_uniform("normals", 3);
      mlsKernel.use(false);
   }
   
   virtual void execute(const PConstraints &constraints) {
      
      fbo.on();
      mlsKernel.use(true);
      
      /// Transfer the Q points to the GPU memory
      glActiveTexture(GL_TEXTURE2);
      glBindTexture(GL_TEXTURE_RECTANGLE_NV, texQ);
      glTexSubImage2D(GL_TEXTURE_RECTANGLE_NV,0,0,0,1,ctrlPointsSize,
                      GL_RGB,GL_FLOAT,&constraints.positions[0]);
      mlsKernel.set_uniform("Q", 2);
      
      /// 
      GLenum buffers[] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT};
      glDrawBuffers(2, buffers);
      
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluOrtho2D(0.0, posTexSize, 0.0, posTexSize);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glViewport(0, 0, (GLsizei) posTexSize, (GLsizei) posTexSize);
      
      glBegin(GL_QUADS);
         glTexCoord2d(0,0);        glVertex2f(0, 0);
         glTexCoord2d(1.0,0);      glVertex2f(posTexSize, 0);
         glTexCoord2d(1.0,1.0);    glVertex2f(posTexSize, posTexSize);
         glTexCoord2d(0,1.0);      glVertex2f(0, posTexSize);
      glEnd();
      glFinish();
      mlsKernel.use(false);
      
      glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
      glReadPixels(0, 0, posTexSize, posTexSize, GL_RGB, GL_FLOAT, &ipositions[0]);
      glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
      glReadPixels(0, 0, posTexSize, posTexSize, GL_RGB, GL_FLOAT, &inormals[0]);
      fbo.off();
      
      Vertex_iterator vi = model->vertices_begin();
      for (unsigned int i=0; i<model->nvertices(); ++i, ++vi) {
         vi->point() = ipositions[i];
         vi->normal() = inormals[i];
      }
   }
   
protected:
   Mesh             *model;
   vector<Point3>    ipositions; // initial vertex positions
   vector<Vector3>   inormals;  // initial vertex normal
   
   /// //////////////////////////////
   glslKernel        mlsKernel;  // 
   FBO<2>            fbo;        // the frame buffer object 
   
   int               posTexSize;     // posTexSize x posTexSize : number of vertices
   int               ctrlPointsSize; // number of control points
   GLuint            texP;
   GLuint            texQ;
};

#endif
